class MinemapGlobe {
    /* 
     * option{
     *     contianer,
     *     starBackground,
     * }
     */
    constructor(option) {
        this.container = document.getElementById(option.container);
        this.starBackground = option.starBackground;

        const canvas = document.createElement('canvas');
        canvas.style.position = 'absolute';
        canvas.style.display = 'block';
        this.canvas = canvas;
        this.container.appendChild(canvas);
        this.layers = [];
        this.tilesCach = {};
        this.seqArr = [];
        this.y = 0;
        this.x = 0;

        this.gl = canvas.getContext('webgl'); // to be fixed
        const gl = this.gl;
        if(!this.gl) {
            console.log('Failed to get the rendering context for WebGL!');
            return;
        }

        this.resize();
        window.addEventListener('resize', () => {
            this.resize();
        })

        const program = gl.createProgram();
        this.program = program;

        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, FSHEADER_SOURSE);
        gl.compileShader(fragmentShader);
        gl.attachShader(program, fragmentShader);

        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, VSHEADER_SOURCE);
        gl.compileShader(vertexShader);
        gl.attachShader(program, vertexShader);

        gl.linkProgram(program);
        gl.useProgram(program);
        gl.program = program;
    
        const vertexBuffers = this.initVertexBuffers();
        this.positionVertexBuffer = vertexBuffers.positionVertexBuffer;
        this.texCoordVertexBuffer = vertexBuffers.texCoordVertexBuffer;
        this.normalVertexBuffer = vertexBuffers.normalVertexBuffer;
        this.indexBuffer = vertexBuffers.indexBuffer;
        this.n = vertexBuffers.n;

        this.setLight();

        this.currentAngle = [0.0, 0.0];
        this.currentdis = 8.0;
        this.lngLat = [90.0, 0.0];        

        this.setViewPerspective();
        this.setModelNormal();
        
        this.u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');   
        this.u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix'); 
        this.u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');

        this.a_Position = gl.getAttribLocation(gl.program, 'a_Position');
        this.a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
        this.a_Normal = gl.getAttribLocation(gl.program, 'a_Normal');

        this.u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');        

        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        // gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
        
        this.initEventHandlers();
        this.setTextures();
        this.baseTilesBuffer = this.getZoomedTileBuffer(2, 2.01);
        this.tilesBuffer = this.getZoomedTileBuffer(4, 2.011);  
        
        this.tilesCorners5 = getTilesCorners(5);
    }

    getZoom() {
        return this.currentdis
    }

    setZoom(n) {
        if(typeof(n) === 'number' && n >= 1 && n <= 22) {
            this.currentdis = n;
            this.setViewPerspective();            
        }
    }

    getLngLat() {
        return this.lngLat;
    }

    setLngLat(lngLat) {
        this.currentAngle[1] = 90 - lngLat[0];
        this.currentAngle[0] = lngLat[1];

        this.setModelNormal();
        this.dealLngLat();
    }

    resize() {
        this.width = this.container.offsetWidth;
        this.height = this.container.offsetHeight;

        this.canvas.height = this.height;
        this.canvas.width = this.width;
        this.canvas.style.height = this.height + 'px';
        this.canvas.style.width = this.width + 'px';

        this.setViewPerspective();      
        this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);        
    }

    initVertexBuffers() {
        let latitudeBands = 30;
        let longitudeBands = 30;
        let radius = 2;
        let gl = this.gl;
    
        let vertices = [];
        let normals = [];
        let texCoords = [];

        for (let latNumber=0; latNumber <= latitudeBands; latNumber++) {
            let theta = latNumber * Math.PI / latitudeBands;
            let sinTheta = Math.sin(theta);
            let cosTheta = Math.cos(theta);
    
            for (let longNumber=0; longNumber <= longitudeBands; longNumber++) {
                let phi = longNumber * 2 * Math.PI / longitudeBands;
                let sinPhi = Math.sin(phi);
                let cosPhi = Math.cos(phi);
    
                let x = cosPhi * sinTheta;
                let y = cosTheta;
                let z = sinPhi * sinTheta;
                let s = 1 - (longNumber / longitudeBands);
                let t = 1 - (1 - (latNumber / latitudeBands));
    
                normals.push(x);
                normals.push(y);
                normals.push(z);
                texCoords.push(s);
                texCoords.push(t);
                vertices.push(radius * x);
                vertices.push(radius * y);
                vertices.push(radius * z);
            }
        }
    
        let indices = [];
        for (let latNumber=0; latNumber < latitudeBands; latNumber++) {
            for (let longNumber=0; longNumber < longitudeBands; longNumber++) {
                let first = (latNumber * (longitudeBands + 1)) + longNumber;
                let second = first + longitudeBands + 1;
                indices.push(first);
                indices.push(second);
                indices.push(first + 1);
    
                indices.push(second);
                indices.push(second + 1);
                indices.push(first + 1);
            }
        }
    
        const positionVertexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionVertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    
        const texCoordVertexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texCoordVertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);
    
        const normalVertexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, normalVertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
    
        const indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
    
        return {
            positionVertexBuffer: positionVertexBuffer,
            texCoordVertexBuffer: texCoordVertexBuffer,
            normalVertexBuffer: normalVertexBuffer,
            indexBuffer: indexBuffer,
            n: indices.length
        };
    }
    
    setLight() {
        const u_AmbientColor = this.gl.getUniformLocation(this.gl.program, 'u_AmbientColor');
        this.gl.uniform3f(u_AmbientColor, 0.2, 0.2, 0.2);
    
        const u_LightColor = this.gl.getUniformLocation(this.gl.program, 'u_LightColor');
        const u_LightPosition = this.gl.getUniformLocation(this.gl.program, 'u_LightPosition');
        this.gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
        this.gl.uniform3f(u_LightPosition, 0.0, 0.0, 20.0);
    }

    setViewPerspective() {
        if(!this.viewProjMatrix) {
            this.viewProjMatrix = new Matrix4();
        }
        this.viewProjMatrix.setPerspective(30, this.width / this.height, 0.5, 100);
        this.viewProjMatrix.lookAt(0, 0, this.currentdis + 3, 0, 0, 0, 0, 1, 0);
    }

    setModelNormal() {
        if(!this.modelMatrix) {
            this.modelMatrix = new Matrix4();
        }
        if(!this.normalMatrix) {
            this.normalMatrix = new Matrix4();
        }
        
        this.modelMatrix.setIdentity();

        this.modelMatrix.rotate(this.currentAngle[0], 1.0, 0.0, 0.0);
        this.modelMatrix.rotate(this.currentAngle[1], 0.0, 1.0, 0.0);

        this.normalMatrix.setInverseOf(this.modelMatrix);
        this.normalMatrix.transpose();
    }

    initEventHandlers() {
        let dragging = false;
        let lastX = -1, lastY = -1;
        let currentAngle = this.currentAngle;
        const that = this;
    
        this.canvas.addEventListener('mousedown', function(e) {
            let event = e ? e : window.event;
            let x = event.clientX, y = event.clientY;
            let rect = event.target.getBoundingClientRect();
            if(rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
                lastX = x; lastY = y;
                dragging = true;
            }
        });
    
        // just for test
        this.canvas.addEventListener('contextmenu', (e) => {
            let event = e ? e : window.event;
            if(event.preventDefault) event.preventDefault();  //标准 
            if(event.returnValue) event.returnValue = false;  //IE 
            if(event.stopPropagation) event.stopPropagation();
            if(event.cancelBubble) event.cancelBubble = true;
            let x = event.clientX, y = event.clientY;
            let rect = e.target.getBoundingClientRect();
            if(rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
                const xView = (x - (rect.left + e.target.width / 2)) / (e.target.width / 2);
                const yView = ((rect.top + e.target.height / 2) - y) / (e.target.height / 2);
                console.log(getClickLngLat(xView, yView, this.currentdis + 3, 30, 2, this.lngLat[0], this.lngLat[1], this.canvas.width, this.canvas.height));
            }
        });
    
        this.canvas.addEventListener('mouseup', function(e) {
            dragging = false;
        });
    
        this.canvas.addEventListener('mousemove', function(e) {
            let event = e ? e : window.event
            let x = event.clientX, y = event.clientY;
            if(dragging) {
                let zoomFactor = that.currentdis / 4;
                if (zoomFactor > 1) zoomFactor = 1;
                if (zoomFactor < 0.2) zoomFactor = 0.2;

                let factor = 100 / that.canvas.height * zoomFactor;
                let dx = factor * (x - lastX);
                let dy = factor * (y - lastY);
                currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 85.0), -85.0);
                currentAngle[1] = currentAngle[1] + dx;
    
                // deal with modelMatrix
                that.setModelNormal();
                
                that.dealLngLat();

                that.updateSence();
                if(that.currentdis < 1) {
                    that.getTilesSeq()
                }
            }
            lastX = x;lastY = y;
        });
    
        // not support firefox
        this.canvas.addEventListener('mousewheel', function(e) {
            let event = e ? e : window.event;
            let zoomFactor = 1.2;
            if (that.currentdis < 0.5) {
                zoomFactor = 2;
            }
            if(event.wheelDelta > 0) {
                that.currentdis = (that.currentdis / zoomFactor) < 0 ? 0 : (that.currentdis / zoomFactor);
            }
            else {
                that.currentdis = (that.currentdis * zoomFactor) > 22 ? 22 : (that.currentdis * zoomFactor);
            }
    
            // deal with viewPerspectiveMatrix
            that.setViewPerspective();

            that.updateSence();
            if (that.currentdis < 1) {
                that.getTilesSeq()
            }
        });
    
        // just for test
        document.addEventListener('keydown', function(e) {
            let event = e ? e : window.event;
            let dx = 0, dy = 0;
            switch(event.keyCode) {
                case 37:
                    dx = -10;
                    break;
                case 38:
                    dy = 10;
                    break;
                case 39:
                    dx = 10;
                    break;
                case 40:
                    dy = -10;
                    break;
                default:
                    break;
            }
            currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0);
            currentAngle[1] = currentAngle[1] + dx;
    
            // deal with modelMatrix
            that.setModelNormal();
    
            that.dealLngLat();

            that.updateSence();
            if (that.currentdis < 1) {
                that.getTilesSeq()
            }
        });
    }
    
    dealLngLat() {
        let lngLat = this.lngLat;
        lngLat[0] = 90 -  this.currentAngle[1] % 360;
        if(lngLat[0] > 180) {
            lngLat[0] = -180 + (lngLat[0] - 180);
        }
        else if(lngLat[0] < -180) {
            lngLat[0] = 180 + (lngLat[0] + 180);
        }
        lngLat[1] = 0 +  this.currentAngle[0] % 360;    

        // console.log(lngLat);
    }

    setTextures() {
        const gl = this.gl;
        const texture = gl.createTexture();
        if (!texture) {
            console.log('Failed to create the texture object');
            return false;
        }

        const textureBac = gl.createTexture();
        if (!textureBac) {
            console.log('Failed to create the texture object');
            return false;
        }

        const imageGlobe = new Image();
        if (!imageGlobe) {
            console.log('Failed to create the image object');
            return false;
        }
        imageGlobe.onload = () => { 
            this.texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, this.texture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, imageGlobe);
            this.updateSence();
        };
        imageGlobe.src = './base.png';

        if(this.starBackground) {
            const imageBac = new Image();
            if (!imageBac) {
                console.log('Failed to create the image object');
                return false;
            }
            imageBac.onload = () => { 
                this.textureBac = gl.createTexture();
                gl.bindTexture(gl.TEXTURE_2D, this.textureBac);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, imageBac);
                this.updateSence();                
            };
            imageBac.src = './starBackground1.jpg';
        }
    }

    updateSence() {
        const gl = this.gl;
        gl.enable(gl.DEPTH_TEST);
        gl.disable(gl.BLEND);        

        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        const mvpMatrix = new Matrix4();
        mvpMatrix.set(this.viewProjMatrix);
        mvpMatrix.multiply(this.modelMatrix);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.positionVertexBuffer);
        gl.vertexAttribPointer(this.a_Position, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(this.a_Position);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordVertexBuffer);
        gl.vertexAttribPointer(this.a_TexCoord, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(this.a_TexCoord);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.normalVertexBuffer);
        gl.vertexAttribPointer(this.a_Normal, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(this.a_Normal);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, this.texture);

        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, this.textureBac);

        gl.uniform1i(this.u_Sampler, 0);

        this.setLight();
    
        gl.uniformMatrix4fv(this.u_ModelMatrix, false, this.modelMatrix.elements);
        gl.uniformMatrix4fv(this.u_MvpMatrix, false, mvpMatrix.elements);
        gl.uniformMatrix4fv(this.u_NormalMatrix, false, this.normalMatrix.elements);
        
        gl.drawElements(gl.TRIANGLES, this.n, gl.UNSIGNED_SHORT, 0);  
        
        if(this.starBackground) {
            const modelBacMatrix = new Matrix4();    
            modelBacMatrix.set(this.modelMatrix);
            modelBacMatrix.scale(12.5, 12.5, 12.5);
            mvpMatrix.set(this.viewProjMatrix);
            mvpMatrix.multiply(modelBacMatrix);
    
            gl.uniformMatrix4fv(this.u_ModelMatrix, false, modelBacMatrix.elements);
            gl.uniformMatrix4fv(this.u_MvpMatrix, false, mvpMatrix.elements);
            
            gl.uniform1i(this.u_Sampler, 1);
            gl.drawElements(gl.TRIANGLES, this.n, gl.UNSIGNED_SHORT, 0);   
        }    

        // if(this.layers.length) {
        //     gl.enable(gl.BLEND);
        //     gl.disable(gl.DEPTH_TEST);

        //     const modelLayerMatrix = new Matrix4();    
        //     modelLayerMatrix.set(this.modelMatrix);
        //     modelLayerMatrix.scale(1.01, 1.01, 1.01);
        //     mvpMatrix.set(this.viewProjMatrix);
        //     mvpMatrix.multiply(modelLayerMatrix);
    
        //     gl.uniformMatrix4fv(this.u_ModelMatrix, false, modelLayerMatrix.elements);
        //     gl.uniformMatrix4fv(this.u_MvpMatrix, false, mvpMatrix.elements);
            
        //     gl.uniform1i(this.u_Sampler, 2);
        //     gl.drawElements(gl.TRIANGLES, this.n, gl.UNSIGNED_SHORT, 0);  
        // }

        // if(this.tiles.length) {
        //     for(let i = 0; i < this.tiles.length; i++) {
        //         // if(this.tiles[i].tileTexture.hasLoad && this.tiles[i].x === this.x && this.tiles[i].y === this.y) {
        //         if(this.tiles[i].tileTexture.hasLoad) {
        //                 this.renderTile(this.tiles[i].positionVertexBuffer, this.tiles[i].texCoordVertexBuffer, this.tiles[i].normalVertexBuffer, this.tiles[i].indexBuffer, this.tiles[i].n, this.tiles[i].tileTexture)
        //         }
        //     }
        // }

        for(let i in this.baseTilesBuffer) {
            if(this.baseTilesBuffer[i].tileTexture.hasLoad) {
                this.renderTile(this.baseTilesBuffer[i].positionVertexBuffer, this.baseTilesBuffer[i].texCoordVertexBuffer, this.baseTilesBuffer[i].normalVertexBuffer, this.baseTilesBuffer[i].indexBuffer, this.baseTilesBuffer[i].n, this.baseTilesBuffer[i].tileTexture)
            }
        }

        for(let i in this.tilesBuffer) {
            if(this.tilesBuffer[i].tileTexture.hasLoad) {
                this.renderTile(this.tilesBuffer[i].positionVertexBuffer, this.tilesBuffer[i].texCoordVertexBuffer, this.tilesBuffer[i].normalVertexBuffer, this.tilesBuffer[i].indexBuffer, this.tilesBuffer[i].n, this.tilesBuffer[i].tileTexture)
            }
        }

        if(this.currentdis < 1 && this.seqArr.length) {
            for (let i = 0; i < this.seqArr.length; i++) {
                const tilesBuffer = this.tilesCach[this.seqArr[i]];
                if (tilesBuffer.tileTexture.hasLoad) {
                    this.renderTile(tilesBuffer.positionVertexBuffer, tilesBuffer.texCoordVertexBuffer, tilesBuffer.normalVertexBuffer, tilesBuffer.indexBuffer, tilesBuffer.n, tilesBuffer.tileTexture)
                }
            }
        }
        // requestAnimationFrame(this.updateSence.bind(this));        
    }

    renderTile(positionVertexBuffer, texCoordVertexBuffer, normalVertexBuffer, indexBuffer, n, tileTexture) {
        const gl = this.gl;
        const mvpMatrix = new Matrix4();
        mvpMatrix.set(this.viewProjMatrix);
        mvpMatrix.multiply(this.modelMatrix);

        // gl.enable(gl.BLEND);
        // gl.disable(gl.DEPTH_TEST);

        gl.bindBuffer(gl.ARRAY_BUFFER, positionVertexBuffer);
        gl.vertexAttribPointer(this.a_Position, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(this.a_Position);

        gl.bindBuffer(gl.ARRAY_BUFFER, texCoordVertexBuffer);
        gl.vertexAttribPointer(this.a_TexCoord, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(this.a_TexCoord);

        gl.bindBuffer(gl.ARRAY_BUFFER, normalVertexBuffer);
        gl.vertexAttribPointer(this.a_Normal, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(this.a_Normal);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

        gl.activeTexture(gl.TEXTURE3);
        gl.bindTexture(gl.TEXTURE_2D, tileTexture);

        gl.uniform1i(this.u_Sampler, 3);

        gl.uniformMatrix4fv(this.u_ModelMatrix, false, this.modelMatrix.elements);
        gl.uniformMatrix4fv(this.u_MvpMatrix, false, mvpMatrix.elements);
        gl.uniformMatrix4fv(this.u_NormalMatrix, false, this.normalMatrix.elements);

        gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);
    }

    getZoomedTileBuffer(n, radius) {
        const tilesCorners = getTilesCorners(n);
        const tileBuffer = {};
        for(let i in tilesCorners) {
            tileBuffer[i] = loadTextureTest(radius, this, n, tilesCorners[i].i, tilesCorners[i].j, tilesCorners[i].lbLngLat, tilesCorners[i].rtLngLat, tilesCorners[i].lbMercator, tilesCorners[i].rtMercator);
        }
        return tileBuffer;
    }

    getTilesSeq() {
        const level = 5;
        const LngLat = this.getLngLat();
        let halfWidth = 20;
        let halfHeight = 20;
        const seqArr = [];

        const lbLng = LngLat[0] - halfWidth;
        const lblat = LngLat[1] - halfHeight;
        const rtLng = LngLat[0] + halfWidth;
        const rtlat = LngLat[1] + halfHeight;

        if (lbLng < -180) lbLng = 180 + (lbLng + 180);
        if (lbLng > 180) lbLng = -180 + (lbLng - 180);
        if (rtLng < -180) rtLng = 180 + (rtLng + 180);
        if (rtLng > 180) rtLng = -180 + (rtLng - 180);
        if (lblat < -85) lblat = -85;
        if (rtlat > 85) rtlat = 85;

        const lbMac = lonLat2WebMercator(lbLng, lblat);
        const rtMac = lonLat2WebMercator(rtLng, rtlat);

        const unit = 20037508.3427892 * 2 / 32;
        const lbSeqX = parseInt((lbMac[0] + 20037508.3427892) / unit);
        const lbSeqY = parseInt((lbMac[1] + 20037508.3427892) / unit);
        const rtSeqX = parseInt((rtMac[0] + 20037508.3427892) / unit);
        const rtSeqY = parseInt((rtMac[1] + 20037508.3427892) / unit);

        for (let i = lbSeqX; i <= rtSeqX; i++) {
            for (let j = lbSeqY; j <= rtSeqY; j++) {
                if (!this.tilesCach[i + '+' + j]) {
                    this.tilesCach[i + '+' + j] = loadTextureTest(2.0111, this, 5, i, j, this.tilesCorners5[i + '+' + j].lbLngLat, this.tilesCorners5[i + '+' + j].rtLngLat, this.tilesCorners5[i + '+' + j].lbMercator, this.tilesCorners5[i + '+' + j].rtMercator);
                }
                seqArr.push(i + '+' + j);
            }
        }

        this.seqArr = seqArr;
    }
}

const VSHEADER_SOURCE = 
    'attribute vec4 a_Position;\n' +
    'attribute vec2 a_TexCoord;\n' +    
    'attribute vec4 a_Normal;\n' +

    'uniform mat4 u_ModelMatrix;\n' +
    'uniform mat4 u_MvpMatrix;\n' +
    'uniform mat4 u_NormalMatrix;\n' +
    'uniform vec3 u_LightColor;\n' +
    'uniform vec3 u_LightPosition;\n' +
    'uniform vec3 u_AmbientColor;\n' +

    'varying vec3 v_Light;\n' +
    'varying vec2 v_TexCoord;\n' + 

    'void main() {\n' +
    '   gl_Position = u_MvpMatrix * a_Position;\n' +
    '   vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n' +
    '   vec4 vertexPosition = u_ModelMatrix * a_Position;\n' +
    '   vec3 lightDirection = normalize(u_LightPosition - vec3(vertexPosition));\n' +
    '   float nDotL = max(dot(lightDirection, normal), 0.0);\n' +
    '   vec3 diffuse = u_LightColor * nDotL;\n' +
    '   vec3 ambient = u_AmbientColor;\n' +
    '   v_Light = diffuse + ambient;\n' +
    '   v_TexCoord = a_TexCoord;\n' +    
    '}\n';

const FSHEADER_SOURSE = 
    'precision mediump float;\n' +
    'varying vec3 v_Color;\n' +

    'varying vec2 v_TexCoord;\n' +    
    'varying vec3 v_Light;\n' +

    'uniform sampler2D u_Sampler;\n' +
    
    'void main() {\n' +
    '   vec4 textureColor = texture2D(u_Sampler, v_TexCoord);\n' +
    '   gl_FragColor = vec4(textureColor.rgb * v_Light, textureColor.a);\n' +
    '}\n';

function getClickLngLat(xView, yView, dis, angle, radius, lng, lat, width, height) {
    const halfPerspectiveHeight = dis * Math.sin(angle / 2 / 180 * Math.PI);
    const halfPerspectiveWidth = dis * Math.sin((angle * width / height) / 2 / 180 * Math.PI);
    
    const x = xView * halfPerspectiveWidth;
    const y = yView * halfPerspectiveHeight;

    const Theta = Math.acos(y / radius);
    const Phi = Math.acos(x / radius / Math.sin(Theta)); 

    const detaLat = (Math.PI / 2 - Theta) / Math.PI * 180;
    const detaLng = (Math.PI / 2 - Phi) / Math.PI * 180;

    const newLat = lat + detaLat;
    const newLng = lng + detaLng;

    return [newLng, newLat];
}

function getTilesCorners(n) {
    const tilesCorners = {};
    const s = 20037508.3427892;
    const devide = Math.pow(2, n);
    const unit = s * 2 / devide; 
    for(let i = 0; i < devide; i++) {
        for(let j = 0; j < devide; j++) {
            tilesCorners[i + '+' + j] = {
                i: i,
                j: j,
                lbMercator: [-20037508.3427892 + i * unit, -20037508.3427892 + j * unit],
                rtMercator: [-20037508.3427892 + (i + 1) * unit, -20037508.3427892 + (j + 1) * unit],
                lbLngLat: WebMercator2lonLat(-20037508.3427892 + i * unit, -20037508.3427892 + j * unit),
                rtLngLat: WebMercator2lonLat(-20037508.3427892 + (i + 1) * unit, -20037508.3427892 + (j + 1) * unit),
            }
        }
    }
    return tilesCorners;
}

function lonLat2WebMercator(lng, lat) {
    const x = lng * 20037508.3427892 / 180;
    let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
    y = y * 20037508.3427892 / 180;
    return [x, y] ;
}

function WebMercator2lonLat(x, y) {
    const lng = x / 20037508.3427892 * 180;
    let lat = y / 20037508.3427892 * 180;
    lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
    return [lng, lat];
}

function lngLat2ThetaPhiXYZ(lngLat) {
    const theta = 90 - lngLat[1];
    const phi = 180 - lngLat[0];
    const sinTheta = Math.sin(theta / 180 * Math.PI);
    const cosTheta = Math.cos(theta / 180 * Math.PI);
    const sinPhi = Math.sin(phi / 180 * Math.PI);
    const cosPhi = Math.cos(phi / 180 * Math.PI);
    const x = cosPhi * sinTheta;
    const y = cosTheta;
    const z = sinPhi * sinTheta;
    
    return {
        theta: theta,
        phi: phi,
        x: x,
        y: y,
        z: z
    };
}

function loadTextureTest(radius, globe, level, i, j, lbLngLat, rtLngLat, lbMercator, rtMercator) {
    const gl = globe.gl;
    const devide = Math.pow(2, level);
    const unit = 20037508.3427892 * 2 / devide;

    const vertices = [];
    const normals = [];
    const texCoords = [];

    const lbLatUnit = modAfterCeil(lbLngLat[1], 6);
    const rtLatUnit = modAfterFloor(rtLngLat[1], 6);
    const lbLngUnit = modAfterCeil(lbLngLat[0], 12);
    const rtLngUnit = modAfterFloor(rtLngLat[0], 12);

    let latitudeBands = Math.floor((rtLatUnit - lbLatUnit) / 6);
    let longitudeBands = Math.floor((rtLngUnit - lbLngUnit) / 12);

    if (latitudeBands < 0) latitudeBands = -1;
    if (longitudeBands < 0) longitudeBands = -1;

    for (let latNumber = 0; latNumber <= latitudeBands + 2; latNumber++) {
        for (let longNumber = 0; longNumber <= longitudeBands + 2; longNumber++) {
            let lat = -1;
            let lng = -1;
            if(latNumber === 0) {
                lat = lbLngLat[1];
            } else if(latNumber === latitudeBands + 2) {
                lat = rtLngLat[1];
            } else {
                let mod = Math.ceil(lbLngLat[1]) % 6;
                if (lbLngLat[1] < 0) {
                    lat = Math.ceil(lbLngLat[1]) + Math.abs(mod) + (latNumber - 1) * 6;
                } else {
                    lat = Math.ceil(lbLngLat[1]) + (6 - Math.abs(mod)) + (latNumber - 1) * 6;
                }
            }
            if(longNumber === 0) {
                lng = lbLngLat[0];
            } else if(longNumber === longitudeBands + 2) {
                lng = rtLngLat[0];
            } else {
                let mod = Math.ceil(lbLngLat[0]) % 12;
                if (lbLngLat[0] < 0) {
                    lng = Math.ceil(lbLngLat[0]) + Math.abs(mod) + (longNumber - 1) * 12;
                } else {
                    lng = Math.ceil(lbLngLat[0]) + (12 - Math.abs(mod)) + (longNumber - 1) * 12;
                }
            }

            const thetaPhiXYZ = lngLat2ThetaPhiXYZ([lng, lat]);
            const mecator = lonLat2WebMercator(lng, lat);

            let x = thetaPhiXYZ.x;
            let y = thetaPhiXYZ.y;
            let z = thetaPhiXYZ.z;
            let s = (mecator[0] - lbMercator[0]) / unit;
            let t = (rtMercator[1] - mecator[1]) / unit;
            
            normals.push(x);
            normals.push(y);
            normals.push(z);
            texCoords.push(s);
            texCoords.push(t);
            vertices.push(radius * x);
            vertices.push(radius * y);
            vertices.push(radius * z);
        }
    }

    const indices = [];
    for (let latNumber = 0; latNumber < latitudeBands + 2; latNumber++) {
        for (let longNumber = 0; longNumber < longitudeBands + 2; longNumber++) {
            let first = (latNumber * (longitudeBands + 3)) + longNumber;
            let second = first + longitudeBands + 3;
            indices.push(first);
            indices.push(second);
            indices.push(first + 1);

            indices.push(second);
            indices.push(second + 1);
            indices.push(first + 1);
        }
    }

    const positionVertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionVertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

    const texCoordVertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordVertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);

    const normalVertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, normalVertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);

    const indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

    const imageTile = new Image();
    if (!imageTile) {
        console.log('Failed to create the image object');
        return false;
    }
    const tileTexture = gl.createTexture();
    tileTexture.hasLoad = false;
    imageTile.onload = () => {
        gl.bindTexture(gl.TEXTURE_2D, tileTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, imageTile);
        tileTexture.hasLoad = true;
        globe.updateSence();
    };
    imageTile.crossOrigin = 'anonymous';
    // imageTile.src = 'http://p1.map.gtimg.com/sateTiles/' + level + '/' + Math.floor(i / 16) + '/' + Math.floor(j / 16) + '/' + i + '_' + j + '.jpg?version=228';
    // imageTile.src = '//api.mapbox.com/v4/mapbox.streets-satellite/' + level + '/' + i + '/' + (devide -1 - j) + '.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NDg1bDA1cjYzM280NHJ5NzlvNDMifQ.d6e-nNyBDtmQCVwVNivz7A';
    imageTile.src = '//a.tile.openstreetmap.org/' + level + '/' + i + '/' + (devide - 1 - j) + '.png';
    return {
        level: level,
        x: i,
        y: j,
        positionVertexBuffer: positionVertexBuffer,
        texCoordVertexBuffer: texCoordVertexBuffer,
        normalVertexBuffer: normalVertexBuffer,
        indexBuffer: indexBuffer,
        n: indices.length,
        tileTexture: tileTexture
    }
}

function modAfterCeil(number, modNumber) {
    let mod = Math.ceil(number) % modNumber;
    if (number < 0) {
        return Math.ceil(number) + Math.abs(mod);
    } else {
        return Math.ceil(number) + (modNumber - Math.abs(mod));
    }
}

function modAfterFloor(number, modNumber) {
    let mod = Math.floor(number) % modNumber;
    if (number < 0) {
        return Math.floor(number) - (modNumber - Math.abs(mod));
    } else {
        return Math.floor(number) - mod;
    }
}

function getTilesSeq(level, LngLat) {
    let halfWidth = 20;
    let halfHeight = 20;

    const seqArr = [];

    const lbLng = LngLat[0] - halfWidth;
    const lblat = LngLat[1] - halfHeight;
    const rtLng = LngLat[0] + halfWidth;
    const rtlat = LngLat[1] + halfHeight;

    if (lbLng < -180) lbLng = 180 + (lbLng + 180);
    if (lbLng > 180) lbLng = -180 + (lbLng - 180);
    if (rtLng < -180) rtLng = 180 + (rtLng + 180);
    if (rtLng > 180) rtLng = -180 + (rtLng - 180);
    if (lblat < -85) lblat = -85;
    if (rtlat > 85) rtlat = 85;

    const lbMac = lonLat2WebMercator(lbLng, lblat);
    const rtMac = lonLat2WebMercator(rtLng, rtlat);

    const unit = 20037508.3427892 * 2 / 32;
    const lbSeqX = parseInt((lbMac[0] + 20037508.3427892) / unit);
    const lbSeqY = parseInt((lbMac[1] + 20037508.3427892) / unit);
    const rtSeqX = parseInt((rtMac[0] + 20037508.3427892) / unit);
    const rtSeqY = parseInt((rtMac[1] + 20037508.3427892) / unit);

    for (let i = lbSeqX; i <= rtSeqX; i++) {
        for (let j = lbSeqY; j <= rtSeqY; j++) {
            if (!this.tilesCach[i + '-' + j]) {
                this.tilesCach[i-j] = loadTextureTest(radius, globe, level, i, j, lbLngLat, rtLngLat, lbMercator, rtMercator);
            }
            seqArr.push(i + '-' + j);
        }
    }

    return seqArr;
}
//http://p1.map.gtimg.com/sateTiles/{z}/{m}/{n}/{x}_{y}.jpg?version=231